/*
 * io with cache utility api
 * @module   util/io
 * @author   genify(caijf@corp.netease.com)
 */
var util = require('util');
var path = require('path');
var fs = require('fs');
var _fs = require('../util/file.js');
var _path = require('../util/path.js');
var _util = require('../util/util.js');
var logger = require('./logger');
// private variable
var _gCache = {};
var _gDwnState = {};
var _gListener = [];
var _gOutState = {};
var _gOutListener = [];
// add event
var _addEvent = function (cache, listeners, callback) {
    // push to listener cache
    if (listeners.indexOf(callback) < 0) {
        listeners.push(callback);
    }
    // check loading state
    if (Object.keys(cache).length <= 0) {
        callback();
    }
};
// remove event
var _removeEvent = function (listeners, callback) {
    var index = listeners.indexOf(callback);
    if (index >= 0) {
        listeners.splice(index, 1);
    }
};
// check event trigger
var _checkEvent = function (cache, listener) {
    if (Object.keys(cache).length <= 0) {
        listener.forEach(function (callback) {
            try {
                callback();
            } catch (ex) {
                console.error(ex.stack);
            }
        });
    }
};
/**
 * dump file content cache
 * @return {Object} file content cache
 */
exports.dump = function () {
    return _gCache;
};
/**
 * add onload event for io
 * @param  {Function} callback - onload callback
 * @return {Void}
 */
exports.onload = function (callback) {
    _addEvent(_gDwnState, _gListener, callback);
};
/**
 * remove onload callback
 * @param  {Function} callback - onload callback
 * @return {Void}
 */
exports.offload = function (callback) {
    _removeEvent(_gListener, callback);
};
/**
 * get resource content with uri
 * @param  {String}   uri      - resource uri
 * @param  {Function} callback - callback if resource loaded from fs or server
 * @return {Boolean}  whether uri loading queue
 */
exports.get = (function () {
    // format content
    var _doFormatContent = function (uri, content, callback) {
        var event = {value: content};
        // pre process file content
        // update file content in callback
        try {
            callback(event);
        } catch (ex) {
            // ignore
            console.error(ex.stack);
        }
        this.cache(uri, event.value);
    };
    // check download state
    var _doCheckDownloadState = function () {
        _checkEvent(_gDwnState, _gListener);
    };
    return function (uri, callback, charset) {
        // check cache
        var content = this.getFromCache(uri);
        if (content != null || !!_gDwnState[uri]) {
            return !0;
        }
        // download file content
        _gDwnState[uri] = !0;
        var handler = function (content) {
            content = content || '';
            if (util.isArray(content)) {
                content = content.join('\n');
            }
            _doFormatContent.call(
                this, uri, content, callback
            );
            delete _gDwnState[uri];
            _doCheckDownloadState();
        }.bind(this);
        // load file from file system or remote server
        try {
            if (!_path.isURL(uri)) {
                _fs.readAsync(uri, handler, charset);
            } else {
                this.download(uri + '?v=' + (+new Date), handler);
            }
        } catch (ex) {
            handler("cant load file " + uri + ' for ' + ex.message + '\n' + ex.stack);
            console.error(ex.stack);
        }
    };
})();
/**
 * get resource content from cache
 * @param  {String} uri - resource uri
 * @return {String} resource content
 */
exports.getFromCache = function (uri) {
    return _gCache[uri];
};
/**
 * cache resource content
 * @param  {String} uri     - resource uri
 * @param  {String} content - resource content
 * @return {Void}
 */
exports.cache = function (uri, content) {
    _gCache[uri] = content;
};
/**
 * fill uri list with content
 * @param  {Array}  list - uri list
 * @param  {String} sep  - list separate char
 * @return {String} content of list
 */
exports.fill = function (list, sep) {
    var ret = [];
    (list || []).forEach(function (uri) {
        var content = this.getFromCache(uri) || '';
        if (!!content) {
            ret.push(content);
        }
    }, this);
    return ret.join(sep || '\n');
};
/**
 * download content from server
 * @param  {String}   uri      - resource content
 * @param  {Function} callback - callback after resource loaded
 * @return {Void}
 */
exports.download = function (uri, callback) {
    var https = /^https:\/\//i.test(uri);
    require(https ? 'https' : 'http').get(
        uri, function (res) {
            // if not set this encoding, chinese characters maybe corrupted
            res.setEncoding('utf8');
            var ret = [];
            res.on('data', function (chunk) {
                ret.push(chunk.toString());
            });
            res.on('end', function () {
                callback(ret.join(''));
            });
        }
    ).on(
        'error', function (error) {
            callback(util.format(
                'download file %s error, cause:\n%s',
                uri, error.message
            ));
        }
    );
};
/**
 * add output done event
 * @param  {Function} callback - done callback
 * @return {Void}
 */
exports.ondone = function (callback) {
    _addEvent(_gOutState, _gOutListener, callback);
};
/**
 * remove output done event
 * @param  {Function} callback - done callback
 * @return {Void}
 */
exports.offdone = function (callback) {
    _removeEvent(_gOutListener, callback);
};
/**
 * output file content
 * @param  {String} file    - file path
 * @param  {String} content - file content
 * @param  {String=} charset - charset
 * @return {Void}
 */
exports.output = function (file, content, charset) {
    var id = file + 'v' + _util.increment();
    _gOutState[id] = !0;
    _fs.mkdir(path.dirname(file));
    _fs.writeAsync(
        file, content, charset,
        function () {
            delete _gOutState[id];
            _checkEvent(_gOutState, _gOutListener);
        }
    );
};
/**
 * output file content synchronous
 * @param  {String} file    - file path
 * @param  {String} content - file content
 * @param  {String=} charset - charset
 * @return {Void}
 */
exports.outputSync = function (file, content, charset) {
    _fs.mkdir(path.dirname(file));
    _fs.write(file, content, charset);
};
/**
 * save resource version
 * @param  {String} uri - uri
 * @param  {String} version - version
 * @return {Void}
 */
exports.resource = function (uri, version) {
    var cache = _gCache.resources;
    if (!cache) {
        cache = {};
        _gCache.resources = cache;
    }
    cache[uri] = version;
};
/**
 * dump resource version cache
 * @return {Object} version cache
 */
exports.dumpResource = function () {
    return _gCache.resources || {};
};
/**
 * 下载文件
 * @param  {String} url - 下载url
 * @param  {String} dest - 写文件的目标路径
 * @param  {Function} [cb] - 回调
 */
exports.downloadFile = function (url, dest, cb) {
    var https = /^https:\/\//i.test(url);
    var file = fs.createWriteStream(dest);
    logger.log('debug', {
        message: `开始下载文件: ${dest}`
    });
    require(https ? 'https' : 'http').get(url, function (res) {
        res.pipe(file);
        file.on('finish', function () {
            logger.log('debug', {
                message: `文件下载完成: ${dest}`
            });
            file.close(cb);
        });
    }).on('error', function (err) {
        fs.unlink(dest);
        cb && cb(err.message);
    });
}